﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.Runtime.InteropServices;

namespace Hook
{
    public class CHoolKey : IDisposable
    {
        private CHook _Hook;
        private Hook.CWindowsAPI.EventProcessMode _EventProcessMode;

        /// <summary>
        /// Initializes a new instance of the KeyListener class.
        /// </summary>
        /// <param name="eventProcessMode">
        /// The process mode in which the event should happen.
        /// </param>
        public CHoolKey(Hook.CWindowsAPI.EventProcessMode eventProcessMode)
        {
            this._Hook = new CHook(Hook.CWindowsAPI.HookType.KEYBOARD_LL, eventProcessMode);
            this._Hook.Callback += new EventHandler<CallbackEventArgs>(this.KeyPressCallback);

            this._EventProcessMode = eventProcessMode;
        }

        /// <summary>
        /// Event queue for all listeners interested in KeyDown events.
        /// </summary>
        public event EventHandler<MyKeyEventArgs> KeyDown;

        /// <summary>
        /// Event queue for all listeners interested in KeyUp events.
        /// </summary>
        public event EventHandler<MyKeyEventArgs> KeyUp;

        #region Disposable

        /// <summary>
        /// Finalizes an instance of the KeyListener class.
        /// </summary>
        ~CHoolKey()
        {
            _Hook.Dispose();
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            _Hook.Dispose();
            GC.SuppressFinalize(this);
        }

        #endregion IDisposable Members

        /// <summary>
        /// Occurs when a key is pressed.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// The CallbackEventArgs instance containing the event data.
        /// </param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "Native.KeyboardEvents.GetKeyboardState(System.Byte[])")]
        private void KeyPressCallback(object sender, CallbackEventArgs e)
        {
            Hook.CWindowsAPI.KeyboardMessages _KeyboardMessages = (Hook.CWindowsAPI.KeyboardMessages)e.WordParam;

            // Copy the data into KeyboardHookStruct
            CWindowsAPI.KeyboardHookStruct _KeyboardHookStruct = (CWindowsAPI.KeyboardHookStruct)Marshal.PtrToStructure(e.LongParam, typeof(CWindowsAPI.KeyboardHookStruct));

            if (_KeyboardMessages == Hook.CWindowsAPI.KeyboardMessages.KeyDown)
            {
                MyKeyEventArgs _KeyEventArgs = new MyKeyEventArgs(_KeyboardHookStruct.vkCode);
                OnKeyDown(_KeyEventArgs);
                e.Handled = _KeyEventArgs.Handled;
            }
            else if (_KeyboardMessages == Hook.CWindowsAPI.KeyboardMessages.SystemKeyDown)
            {
                MyKeyEventArgs _KeyEventArgs = new MyKeyEventArgs(_KeyboardHookStruct.vkCode, true);
                OnKeyDown(_KeyEventArgs);
                e.Handled = _KeyEventArgs.Handled;
            }
            else if (_KeyboardMessages == Hook.CWindowsAPI.KeyboardMessages.KeyUp)
            {
                MyKeyEventArgs _KeyEventArgs = new MyKeyEventArgs(_KeyboardHookStruct.vkCode);
                OnKeyUp(_KeyEventArgs);
                e.Handled = _KeyEventArgs.Handled;
            }
            else if (_KeyboardMessages == Hook.CWindowsAPI.KeyboardMessages.SystemKeyUp)
            {
                MyKeyEventArgs _KeyEventArgs = new MyKeyEventArgs(_KeyboardHookStruct.vkCode, true);
                OnKeyUp(_KeyEventArgs);
                e.Handled = _KeyEventArgs.Handled;
            }

            if (!e.Handled && _KeyboardMessages == Hook.CWindowsAPI.KeyboardMessages.KeyDown)
            {
                // TODO: Implement keypress behaviour.
            }

            if (_EventProcessMode == Hook.CWindowsAPI.EventProcessMode.ApplicationProcess)
            {
                e.Handled = true;
            }
        }

        /// <summary> Raises the key down event. </summary>
        /// <remarks> Eyal Shilony, 15/08/2010. </remarks>
        /// <param name="e"> The <see cref="System.Windows.Forms.KeyEventArgs"/> instance containing the event data. </param>
        protected virtual void OnKeyDown(MyKeyEventArgs e)
        {
            EventHandler<MyKeyEventArgs> _Handler = KeyDown;
            if (_Handler != null)
            {
                _Handler(this, e);
            }
        }

        /// <summary> Raises the key up event. </summary>
        /// <param name="e">
        /// The KeyEventArgs instance containing the event data.
        /// </param>
        protected virtual void OnKeyUp(MyKeyEventArgs e)
        {
            EventHandler<MyKeyEventArgs> _Handler = KeyUp;
            if (_Handler != null)
            {
                _Handler(this, e);
            }
        }

        public static class KeyboardKeyHelper
        {
            /// <summary>
            ///	uCode is a virtual-key code and is translated into a scan code. If it is a virtual-key code that does not distinguish between left- and right-hand keys, the left-hand scan code is returned. If there is no translation, the function returns 0.
            /// </summary>
            private const int MapVKToVSC = 0x00;	// MAPVK_VK_TO_VSC

            /// <summary>
            /// Determines whether the shift key is down.
            /// </summary>
            /// <returns>
            /// 	<c>true</c> if the shift key is down; otherwise, <c>false</c>.
            /// </returns>
            public static bool IsShiftKeyDown()
            {
                return IsModifierKeyDown(Key.LeftShift, Key.RightShift);
            }

            /// <summary>
            /// Determines whether the alt key is down.
            /// </summary>
            /// <returns>
            /// 	<c>true</c> if the alt key is down; otherwise, <c>false</c>.
            /// </returns>
            public static bool IsAltKeyDown()
            {
                return IsModifierKeyDown(Key.LeftAlt, Key.RightAlt);
            }

            /// <summary>
            /// Determines whether the control key is down.
            /// </summary>
            /// <returns>
            /// 	<c>true</c> if the control key is down; otherwise, <c>false</c>.
            /// </returns>
            public static bool IsControlKeyDown()
            {
                return IsModifierKeyDown(Key.LeftCtrl, Key.RightCtrl);
            }

            /// <summary>
            /// Determines whether the caps lock key is turned on.
            /// </summary>
            /// <returns>
            /// 	<c>true</c> if the caps lock key is turned on; otherwise, <c>false</c>.
            /// </returns>
            public static bool IsCapsLockOn()
            {
                return CWindowsAPI.GetKeyState(Key.CapsLock) != 0;
            }

            public static string GetCharacter(int virtualKey)
            {
                return GetUnicodeCharacter(virtualKey);
            }

            internal static string GetUnicodeCharacter(int virtualKey)
            {
                IntPtr layout = CWindowsAPI.GetKeyboardLayout(0);

                if (layout != IntPtr.Zero)
                {
                    byte[] lpKeyState = new byte[256];

                    int uScanCode = CWindowsAPI.MapVirtualKeyEx(virtualKey, MapVKToVSC, layout);
                    bool keyboardState = CWindowsAPI.GetKeyboardState(lpKeyState);

                    if (keyboardState)
                    {
                        StringBuilder sb = new StringBuilder(10);

                        if (CWindowsAPI.ToUnicodeEx(virtualKey, uScanCode, lpKeyState, sb, sb.Capacity, 0, layout) == 1)
                        {
                            return sb.ToString();
                        }
                    }
                }

                return string.Empty;
            }

            private static bool IsModifierKeyDown(Key virtualKey, Key key)
            {
                Key none = Key.None;

                if (CWindowsAPI.GetKeyState(virtualKey) < 0)
                {
                    none |= key;
                }

                return (none & key) == key;
            }
        }
    }
}